copp\copp\copp2/formulation.rs
1//! Problem data models and builders for second-order path parameterization.
2//!
3//! # Method identity
4//! This module defines validated formulation objects for:
5//! - **Time-Optimal Path Parameterization (TOPP2)**,
6//! - **Convex-Objective Path Parameterization (COPP2)**.
7//!
8//! # Discrete variables (shared notation)
9//! On a station grid with closed index interval `[idx_s_start, idx_s_final]`:
10//! - state profile is `a(s)=\dot{s}^2`;
11//! - boundary tuple is `a_boundary = (a_start, a_final)`;
12//! - station count is `s_len = idx_s_final - idx_s_start + 1`.
13//!
14//! # High-level pipeline
15//! 1. Construct `Topp2ProblemBuilder` or `Copp2ProblemBuilder` from caller data.
16//! 2. Run builder validation (index interval, bounds, objective compatibility).
17//! 3. Build immutable problem objects used by DP/optimization backends.
18
19use crate::copp::constraints::Constraints;
20use crate::copp::{CoppObjective, validate_copp2_objectives};
21use crate::diag::{CoppError, check_non_negative, check_s_interval_valid};
22use crate::robot::robot_core::{Robot, RobotBasic, RobotTorque};
23
24/// Formulated TOPP2 problem data.
25///
26/// # Fields
27/// - `constraints`: path-dependent kinematic/dynamic bounds on the selected interval;
28/// - `idx_s_interval`: closed station-index interval `[idx_s_start, idx_s_final]`;
29/// - `a_boundary`: endpoint state tuple `(a_start, a_final)`.
30pub struct Topp2Problem<'a> {
31 pub(crate) constraints: &'a Constraints,
32 pub(crate) idx_s_interval: (usize, usize),
33 pub(crate) a_boundary: (f64, f64),
34}
35
36impl<'a> Topp2Problem<'a> {
37 /// Return station count on the closed interval.
38 ///
39 /// For `[idx_s_start, idx_s_final]`, this returns
40 /// `idx_s_final - idx_s_start + 1`.
41 #[inline]
42 pub fn s_len(&self) -> usize {
43 self.idx_s_interval.1 - self.idx_s_interval.0 + 1
44 }
45}
46
47/// Builder for [`Topp2Problem`].
48pub struct Topp2ProblemBuilder<'a> {
49 /// Reference to path constraints (extracted from robot at construction time).
50 pub constraints: &'a Constraints,
51 /// Closed station-index interval `(idx_s_start, idx_s_final)`.
52 pub idx_s_interval: (usize, usize),
53 /// Endpoint state tuple `(a_start, a_final)`.
54 pub a_boundary: (f64, f64),
55}
56
57impl<'a> Topp2ProblemBuilder<'a> {
58 /// Create a TOPP2 builder from a [`Robot`] reference.
59 ///
60 /// # Parameters
61 /// - `robot`: robot wrapper with the trait [`RobotBasic`] whose constraint buffer defines the problem domain.
62 /// - `idx_s_interval`: closed station-index interval `(idx_s_start, idx_s_final)`.
63 /// - `a_boundary`: endpoint state tuple `(a_start, a_final)`.
64 #[inline]
65 pub fn new<M: RobotBasic>(
66 robot: &'a Robot<M>,
67 idx_s_interval: (usize, usize),
68 a_boundary: (f64, f64),
69 ) -> Self {
70 Self {
71 constraints: &robot.constraints,
72 idx_s_interval,
73 a_boundary,
74 }
75 }
76
77 /// Create a TOPP2 builder with all required fields.
78 ///
79 /// # Parameters
80 /// - `constraints`: reference to path constraints defining the problem domain.
81 /// - `idx_s_interval`: closed station-index interval `(idx_s_start, idx_s_final)`.
82 /// - `a_boundary`: endpoint state tuple `(a_start, a_final)`.
83 #[inline]
84 pub fn with_constraint(
85 constraints: &'a Constraints,
86 idx_s_interval: (usize, usize),
87 a_boundary: (f64, f64),
88 ) -> Self {
89 Self {
90 constraints,
91 idx_s_interval,
92 a_boundary,
93 }
94 }
95
96 /// Build a validated [`Topp2Problem`].
97 #[inline]
98 pub fn build(&self) -> Result<Topp2Problem<'a>, CoppError> {
99 self.validate()?;
100 Ok(Topp2Problem {
101 constraints: self.constraints,
102 idx_s_interval: self.idx_s_interval,
103 a_boundary: self.a_boundary,
104 })
105 }
106
107 /// Validate builder fields and consistency.
108 #[inline]
109 pub fn validate(&self) -> Result<(), CoppError> {
110 check_s_interval_valid(
111 "Topp2ProblemBuilder",
112 self.idx_s_interval.0,
113 self.idx_s_interval.1,
114 )?;
115 self.constraints.check_s_in_bounds(
116 self.idx_s_interval.0,
117 self.idx_s_interval.1 - self.idx_s_interval.0 + 1,
118 )?;
119 check_non_negative(
120 "Topp2ProblemBuilder",
121 "a_start (a_boundary.0)",
122 self.a_boundary.0,
123 )?;
124 check_non_negative(
125 "Topp2ProblemBuilder",
126 "a_final (a_boundary.1)",
127 self.a_boundary.1,
128 )?;
129 Ok(())
130 }
131}
132
133/// Formulated COPP2 problem data.
134///
135/// # Fields
136/// - `robot`: robot model supplying constraints and torque-related terms;
137/// - `objectives`: objective list for COPP2 optimization;
138/// - `idx_s_interval`: closed station-index interval `[idx_s_start, idx_s_final]`;
139/// - `a_boundary`: endpoint state tuple `(a_start, a_final)`.
140pub struct Copp2Problem<'a, M: RobotTorque> {
141 pub(crate) robot: &'a Robot<M>,
142 pub(crate) objectives: &'a [CoppObjective<'a>],
143 pub(crate) idx_s_interval: (usize, usize),
144 pub(crate) a_boundary: (f64, f64),
145}
146
147impl<'a, M: RobotTorque> Copp2Problem<'a, M> {
148 /// Return station count on the closed interval.
149 ///
150 /// For `[idx_s_start, idx_s_final]`, this returns
151 /// `idx_s_final - idx_s_start + 1`.
152 #[inline]
153 pub fn s_len(&self) -> usize {
154 self.idx_s_interval.1 - self.idx_s_interval.0 + 1
155 }
156}
157
158/// Builder for [`Copp2Problem`].
159pub struct Copp2ProblemBuilder<'a, M: RobotTorque> {
160 /// Reference to robot model defining constraints and dynamics.
161 pub robot: &'a Robot<M>,
162 /// Closed station-index interval `(idx_s_start, idx_s_final)`.
163 pub idx_s_interval: (usize, usize),
164 /// Endpoint state tuple `(a_start, a_final)`.
165 pub a_boundary: (f64, f64),
166 /// Objectives for COPP2 optimization.
167 pub objectives: &'a [CoppObjective<'a>],
168}
169
170impl<'a, M: RobotTorque> Copp2ProblemBuilder<'a, M> {
171 /// Create a COPP2 builder with all required fields.
172 #[inline]
173 pub fn new(
174 robot: &'a Robot<M>,
175 idx_s_interval: (usize, usize),
176 a_boundary: (f64, f64),
177 objectives: &'a [CoppObjective<'a>],
178 ) -> Self {
179 Self {
180 robot,
181 idx_s_interval,
182 a_boundary,
183 objectives,
184 }
185 }
186
187 /// Build a validated [`Copp2Problem`].
188 #[inline]
189 pub fn build(&self) -> Result<Copp2Problem<'a, M>, CoppError> {
190 self.validate()?;
191 Ok(Copp2Problem {
192 robot: self.robot,
193 idx_s_interval: self.idx_s_interval,
194 a_boundary: self.a_boundary,
195 objectives: self.objectives,
196 })
197 }
198
199 /// Validate builder fields and objective compatibility.
200 #[inline]
201 pub fn validate(&self) -> Result<(), CoppError> {
202 check_s_interval_valid(
203 "Copp2ProblemBuilder",
204 self.idx_s_interval.0,
205 self.idx_s_interval.1,
206 )?;
207 self.robot.constraints.check_s_in_bounds(
208 self.idx_s_interval.0,
209 self.idx_s_interval.1 - self.idx_s_interval.0 + 1,
210 )?;
211 check_non_negative(
212 "Copp2ProblemBuilder",
213 "a_start (a_boundary.0)",
214 self.a_boundary.0,
215 )?;
216 check_non_negative(
217 "Copp2ProblemBuilder",
218 "a_final (a_boundary.1)",
219 self.a_boundary.1,
220 )?;
221
222 let s_len = self.idx_s_interval.1 - self.idx_s_interval.0 + 1;
223 validate_copp2_objectives(
224 "Copp2ProblemBuilder",
225 self.objectives,
226 self.robot.dim(),
227 s_len,
228 )?;
229 Ok(())
230 }
231}
232
233impl<'a, M: RobotTorque> Copp2Problem<'a, M> {
234 /// Convert to the TOPP2 view that shares interval and boundary fields.
235 ///
236 /// This is used by internal stages that only need standard TOPP2 constraints.
237 pub(crate) fn as_topp2_problem(&self) -> Topp2Problem<'a> {
238 Topp2Problem {
239 constraints: &self.robot.constraints,
240 idx_s_interval: self.idx_s_interval,
241 a_boundary: self.a_boundary,
242 }
243 }
244}